package stacking;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintStream;
import java.util.Iterator;
import java.util.List;
import java.util.ArrayList;


import utils.DataManipulation;
import weka.classifiers.Classifier;
import weka.core.Instance;
import weka.core.Instances;
import weka.filters.unsupervised.instance.Randomize;


public class Stacking {
	private Classifier[] baseClassifiers = null;
	private int numberBaseClassifiers = 5; //default value
	private Instances fullBaseDataset = null;
	private Instances baseTrainDataset = null;
	private boolean useSameBaseTrainSetForAllBaseClassifiers;
	private double basePercentSplit;
	
	private Classifier metaClassifier;
	private Instances fullMetaDataset = null;
	private Instances metaTrainDataset = null;
	
	private List<Sample> metaData;
	PrintStream printFile = null;
	private String pathFile;
	
	
	//========================================================================================================================
	// constructor
	//========================================================================================================================
	public Stacking(Instances fullBaseDataset, Classifier[] baseClassifiers, Classifier metaClassifier, double basePercentSplit, boolean useSameBaseTrainSetForAllBaseClassifiers){
		//load base classifiers
		this.numberBaseClassifiers = baseClassifiers.length;
		this.baseClassifiers = new Classifier[this.numberBaseClassifiers];
		for(int i=0;i<this.numberBaseClassifiers;i++){
			try {
				//this.baseClassifiers[i] = Classifier.makeCopy(baseClassifiers[i]);
				this.baseClassifiers[i] = baseClassifiers[i];
			} catch (Exception e) {
				// TODO Auto-generated catch block
				System.out.println("error while making copy of BASE classifiers on: Stacking Constructor function");
				e.printStackTrace();
			}
		}
		
		//load full base dataset
		this.fullBaseDataset = fullBaseDataset;
		
		//
		this.basePercentSplit = basePercentSplit;
		
		//load meta classifier
		try {
			this.metaClassifier = Classifier.makeCopy(metaClassifier);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			System.out.println("error while making copy of META classifier on: Stacking Constructor function");
			e.printStackTrace();
		}
		
		//
		this.useSameBaseTrainSetForAllBaseClassifiers = useSameBaseTrainSetForAllBaseClassifiers;
		
		//
		metaData = new ArrayList<Sample>();
		
		//
		this.pathFile = "metaData.csv";
		
	}
	//========================================================================================================================
	//
	//========================================================================================================================
	// build train set for base classifiers
	//========================================================================================================================
	private Instances buildBaseTrainSet(double percentSplit) throws Exception {
		Instances baseTrainSet = null;
		
		int trainSize = (int) (fullBaseDataset.numInstances()* percentSplit / 100);
		
		Randomize randomize = new weka.filters.unsupervised.instance.Randomize();
		randomize.setRandomSeed(new java.util.Random().nextInt(99) + 1);
		weka.filters.Filter random = randomize;
		random.setInputFormat(fullBaseDataset);
		
		baseTrainSet = weka.filters.Filter.useFilter(fullBaseDataset, random);
		Instances trainSet = new Instances(baseTrainSet, 0, trainSize);
		
		return trainSet;
	}
	//========================================================================================================================
	// train base classifiers
	//========================================================================================================================
	private void trainBaseClassifiers(double percentSplit) throws Exception{
		
		baseTrainDataset = buildBaseTrainSet(percentSplit);
		
		for(int i=0;i<numberBaseClassifiers;i++){
			if(useSameBaseTrainSetForAllBaseClassifiers)
				baseClassifiers[i].buildClassifier(baseTrainDataset);
			else{
				baseTrainDataset = buildBaseTrainSet(percentSplit);
				baseClassifiers[i].buildClassifier(baseTrainDataset);
			}
		}
	}
	//======================================================================================================================== 
	// build full meta dataset
	//========================================================================================================================
	private void buildMetaDataset(){
		
		for(int i=0;i<fullBaseDataset.numInstances();i++){
			Sample sample = new Sample();
			sample.setId(i);
			sample.setClassLabel(Double.toString(fullBaseDataset.instance(i).classValue()));
			
			for(int j=0;j<numberBaseClassifiers;j++){
				
				try {
					double pred = baseClassifiers[j].classifyInstance(fullBaseDataset.instance(i));
					sample.addFeatures(Double.toString(pred));
				} catch (Exception e) {
					// TODO Auto-generated catch block
					System.out.println("error while classifing an instance on: buildMetaDataset()");
					e.printStackTrace();
				}
				
				
			}
			
//			for(int z=0;z<sample.getNumberFeatures();z++)
//				System.out.print(sample.getFeature(z) + ", ");
//			System.out.println(i);
			
			metaData.add(sample);
			
		}
		
		exportToCSV();
		//System.out.println("Aguardando...");
		// toma POG
		Stacking.sleep(1);
		//System.out.println("Pronto!");
		DataManipulation dataManipulation = new DataManipulation(pathFile,"csv");
		//fullMetaDataset = new Instances(dataManipulation.getDataset(), 0, dataManipulation.getDataset().numInstances());
		fullMetaDataset = dataManipulation.getDataset();
		
//		for(int i=0;i<fullMetaDataset.numInstances();i++)
//			System.out.println(fullMetaDataset.instance(i).classValue());
		
	}
	//========================================================================================================================
	// build meta train set
	//========================================================================================================================
	private void buildMetaTrainSet(double percentSplit) throws Exception{
		
		Randomize randomize = new weka.filters.unsupervised.instance.Randomize();
		randomize.setRandomSeed(new java.util.Random().nextInt(99) + 1);
		weka.filters.Filter random = randomize;
		random.setInputFormat(fullMetaDataset);
		
		Instances metaTrain = weka.filters.Filter.useFilter(fullMetaDataset, random);
		int trainSize = (int) (fullMetaDataset.numInstances()* percentSplit / 100);
		metaTrainDataset = new Instances(metaTrain, 0, trainSize);
	}
	//========================================================================================================================
	// return meta dataset
	//========================================================================================================================
	public Instances getMetaDataset(){
		return fullMetaDataset;
	}
	//========================================================================================================================
	// train meta classifier
	//========================================================================================================================
	private void trainMetaClassifier(){
		try {
			metaClassifier.buildClassifier(metaTrainDataset);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			System.out.println("error while training meta classifier on: trainMetaClassifier()");
			e.printStackTrace();
		}
	}
	//========================================================================================================================
	// classify an instance by using the meta classifier
	//========================================================================================================================
	public double classifyInstance(Instance instance) throws Exception{
		return metaClassifier.classifyInstance(instance);
	}
	//======================================================================================================================== 
	// build stacking meta classifier
	//========================================================================================================================
	public void buildStacking(double metaPercentSplit){
		// train base classifiers
		try {
			trainBaseClassifiers(basePercentSplit);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			System.out.println("error while training base classifiers on: buildStacking()");
			e.printStackTrace();
		}
		
		//
		buildMetaDataset();
		try {
			buildMetaTrainSet(metaPercentSplit);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			System.out.println("error while building meta train set on: buildStacking()");
			e.printStackTrace();
		}
		//
		
		trainMetaClassifier();
		
	}
	//========================================================================================================================
	// e tome-lhe P.O.G.
	//========================================================================================================================
	public static void sleep(int n) {
		long t0, t1;

		t0 = System.currentTimeMillis();

		do {
			t1 = System.currentTimeMillis();
		} while ((t1 - t0) < (n * 1000));
	}
	//========================================================================================================================
	// export meta data to CSV weka standard format
	//========================================================================================================================
	private void exportToCSV(){
		try {
			printFile = new PrintStream(new  File(pathFile));
			
			//primeira linha do csv, necessario para o weka
			int n = metaData.get(0).getNumberFeatures();
			String first_line = ""; 
			
			int x;
			for(x=1;x<=n;x++)
				first_line += "w"+x + ",";
			first_line += "class";
			//first_line += x;
			printFile.println(first_line);
			
			Iterator<Sample> i = metaData.iterator();
			while(i.hasNext()){
				String line =  "";

				Sample sample = i.next();
				Iterator<String> sampleFeature = sample.features.iterator();
				while(sampleFeature.hasNext()){
					line += (sampleFeature.next()+",");
				}
				line += (sample.classLabel);
				printFile.println(line);
			}
			
		} catch (FileNotFoundException e) {
			System.out.println("Error while writing the target file..");
			e.printStackTrace();
		}
		finally{
			printFile.close();
		}
		
	}
	//========================================================================================================================

}
